﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace Kokomo.Mirror.CliMetadata.Signatures
{
	class GenericTypeSpec : TypeSpec
	{
		public int typeToken;
		public TypeSpec[] genericArguments;

		public override bool IsComplete
		{
			get
			{
				bool isComplete =
					(typeToken != 0)
					&& (this.genericArguments != null)
					;
				return isComplete;
			}
		}

		public override Type ResolveType(
			Module module,
			bool throwOnError,
			Type[] genericTypeArgs,
			Type[] genericMethodArgs
			)
		{
			if (!this.IsComplete) throw new InvalidOperationException();

			Type baseType = module.ResolveType(this.typeToken);
			if ((object)baseType == null)
			{
				if (throwOnError)
					throw new TypeLoadException();
				else
					return null;
			}

			Type[] baseTypeArgs = baseType.GetGenericArguments();
			Type[] arguments = new Type[this.genericArguments.Length];

			Type[] innerGenericTypeArgs = baseTypeArgs;
			//if (genericTypeArgs != null)
			//    innerGenericTypeArgs = baseTypeArgs.Concat(genericTypeArgs).ToArray();

			for (int i = 0; i < arguments.Length; i++)
			{
				TypeSpec argumentSignature = this.genericArguments[i];

				Type argument = argumentSignature.ResolveType(
					module,
					throwOnError,
					genericTypeArgs,
					genericMethodArgs
					);

				////// HACK: Attempt to resolve problem with ListChunk<Pair<,>>[,].Set(Int32,Int32,...
				//if (argument == null)
				//    argument = argumentSignature.ResolveType(module, baseTypeArgs, genericMethodArgs);
				if ((object)argument == null)
					return null;

				arguments[i] = argument;
			}
			Type genericType = baseType.MakeGenericType(arguments);

			return genericType;
		}

		public override string ToString()
		{
			StringBuilder builder = new StringBuilder();
			builder.Append(CodedIndex.FromToken(this.typeToken));
			builder.Append("<");
			for (int i = 0; i < this.genericArguments.Length; i++)
			{
				if (i > 0) builder.Append(", ");
				builder.Append(this.genericArguments[i]);
			}
			builder.Append(">");
			return builder.ToString();
		}

		public override bool Equals(object obj)
		{
			GenericTypeSpec other = obj as GenericTypeSpec;
			if (other == null) return false;

			bool equals =
				(this.typeToken == other.typeToken)
				&& (this.IsComplete)
				&& (other.IsComplete)
				&& (this.genericArguments.SequenceEqual(other.genericArguments))
				;
			return equals;
		}

		public override bool Matches(
			Type type,
			Module module,
			Type[] genericTypeArguments,
			Type[] genericMethodArguments
			)
		{
			if ((object)type == null) throw new ArgumentNullException("type");

			if (
				!type.IsGenericType
				|| type.IsGenericTypeDefinition
				)
				return false;

			Type definition = module.ResolveType(this.typeToken);
			if (!MemberEqualityComparer.Equal(definition, type.GetGenericTypeDefinition()))
				return false;

			// HACK: Not sure if this is correct, but seems to work
			//Type[] contextGenericArgs = type.GetGenericArguments().Concat(genericTypeArguments).ToArray();
			Type[] genericArgs = type.GetGenericArguments(); //.Concat(genericTypeArguments).ToArray();
			for (int i = 0; i < genericArgs.Length; i++)
			{
				Type genericArg = genericArgs[i];

				if (!this.genericArguments[i].Matches(genericArg, module, genericTypeArguments, genericMethodArguments))
					return false;
			}

			return true;
		}
		public override bool Matches(
			Module ownerModule,
			TypeSpec other,
			Module otherModule
			)
		{
			if ((object)ownerModule == null) throw new ArgumentNullException("ownerModule");
			if (other == null) throw new ArgumentNullException("other");
			if ((object)otherModule == null) throw new ArgumentNullException("otherModule");

			GenericTypeSpec otherGeneric = other as GenericTypeSpec;
			if (otherGeneric == null) return false;

			ModuleMemberReference thisTypeRef = ModuleMemberReference.FromModuleToken(ownerModule, this.typeToken);
			ModuleMemberReference otherTypeRef = ModuleMemberReference.FromModuleToken(otherModule, otherGeneric.typeToken);
			if (!thisTypeRef.IsCanonical)
				thisTypeRef = thisTypeRef.MakeCanonical();
			if (!otherTypeRef.IsCanonical)
				otherTypeRef = otherTypeRef.MakeCanonical();

			bool matches =
				thisTypeRef.Equals(otherTypeRef)
				&& (this.genericArguments.Length == otherGeneric.genericArguments.Length)
				;

			if (matches)
			{
				for (int i = 0; i < this.genericArguments.Length; i++)
				{
					matches = this.genericArguments[i].Matches(
						ownerModule,
						otherGeneric.genericArguments[i],
						otherModule
						);
					if (!matches) break;
				}
			}

			return matches;
		}
	}
}
